package com.ibm.rules.test;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;

import com.google.common.collect.BoundType;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Range;
import com.google.common.collect.RangeMap;
import com.google.common.collect.TreeRangeMap;
import com.ibm.cps.message.ParsedDataMessage;
import com.ibm.rules.Rule;
import com.ibm.rules.RuleEntry;
import com.ibm.rules.tree.RuleTreeNode;
import com.ibm.util.ComparablePair;
import com.ibm.util.ErrorCode;
import com.ibm.util.exception.CPSException;

public class RangeRuleTreeNode extends RuleTreeNode {

	/* suppose splitrange is never overlapped */
	public static class SplitRange<T extends Comparable<T>> implements
			Comparable<SplitRange> {
		T leftbd;
		T rightbd;

		public SplitRange(T lb, T rb) {
			this.leftbd = lb;
			this.rightbd = rb;
		}

		HashSet<BigInteger> refranges = new HashSet<BigInteger>();

		@SuppressWarnings({ "rawtypes", "unchecked" })
		@Override
		public int compareTo(SplitRange o) {
			return leftbd.compareTo((T) o.leftbd);
		}

		public void addRange(RuleEntry<T> r) {
			refranges.add(r.getRuleID());
		}
	}

	public static class TagNode<T extends Comparable<T>> {

		public int tagid;

		public TagNode(int tagid, Collection<RuleEntry<T>> rulesfortag) {
			this.tagid = tagid;
			AddRanges(rulesfortag);
		}

		public int getTagid() {
			return tagid;
		}

		TreeSet<T> splitpointset = new TreeSet<T>();

		public ArrayList<SplitRange<T>> splitrangelist = new ArrayList<SplitRange<T>>();

		/* each split-range mapped to its index in arraylist splitrangelist */
		public RangeMap<T, Integer> splitrange_id_Map = TreeRangeMap
				.<T, Integer> create();

		public static final double EFCLONG_DOUBLE = 1e-16;
		public static final float EFCLONG_FLOAT = (float) 1e-15;

		private void addEntrytoSplitPoint(RuleEntry<T> entry) {
			if (entry.hasLeftBound()) {
				T curval = entry.getLeftBound();
				BoundType bt = entry.getLeftBoundType();
				if (bt == (BoundType.OPEN)) {
					curval = addDelta(curval);
					splitpointset.add(curval);
//					if (curval instanceof Double) {
//						Double curval_v = (Double) curval + EFCLONG_DOUBLE;
//						splitpointset.add((T) curval_v);
//					} else if (curval instanceof Float) {
//						Float curval_v = (Float) curval + EFCLONG_FLOAT;
//						splitpointset.add((T) curval_v);
//					} else if ((curval instanceof Long)) {
//						Long curval_v = (Long) curval + 1;
//						splitpointset.add((T) curval_v);
//					}
				} else {// closed
					splitpointset.add(entry.getLeftBound());
				}
			}
			if (entry.hasRightBound()) {
				T curval = entry.getRightBound();
				BoundType bt = entry.getRightBoundType();
				if (bt == (BoundType.OPEN)) {
					curval = minusDelta(curval);
					splitpointset.add(curval);
					
//					if (curval instanceof Double) {
//						Double curval_v = (Double) curval - EFCLONG_DOUBLE;
//						splitpointset.add((T) curval_v);
//					} else if (curval instanceof Float) {
//						Float curval_v = (Float) curval - EFCLONG_FLOAT;
//						splitpointset.add((T) curval_v);
//					} else if ((curval instanceof Long)) {
//						Long curval_v = (Long) curval - 1;
//						splitpointset.add((T) curval_v);
//					}
				} else {// closed
					splitpointset.add(entry.getRightBound());
				}
			}
		}
		
		private T minusDelta(T curval){
			if (curval instanceof Double) {
				Double curval_v = (Double) curval - EFCLONG_DOUBLE;
				return (T)curval_v;
			} else if (curval instanceof Float) {
				Float curval_v = (Float) curval - EFCLONG_FLOAT;
				return (T)curval_v;
			} else if ((curval instanceof Long)) {
				Long curval_v = (Long) curval - 1;
				return (T)curval_v;
			}
			else if ((curval instanceof Integer)) {
				Integer curval_v = (Integer) curval - 1;
				return (T)curval_v;
			}
			else
				return curval;
		}

		private T addDelta(T curval){
			if (curval instanceof Double) {
				Double curval_v = (Double) curval + EFCLONG_DOUBLE;
				return (T)curval_v;
			} else if (curval instanceof Float) {
				Float curval_v = (Float) curval + EFCLONG_FLOAT;
				return (T)curval_v;
			} else if ((curval instanceof Long)) {
				Long curval_v = (Long) curval + 1;
				return (T)curval_v;
			}
			else if ((curval instanceof Integer)) {
				Integer curval_v = (Integer) curval + 1;
				return (T)curval_v;
			}
			else
				return curval;
			}
		
		
		private T minus2Delta(T curval){
			if (curval instanceof Double) {
				Double curval_v = (Double) curval - EFCLONG_DOUBLE*2;
				return (T)curval_v;
			} else if (curval instanceof Float) {
				Float curval_v = (Float) curval - EFCLONG_FLOAT*2;
				return (T)curval_v;
			} else if ((curval instanceof Long)) {
				Long curval_v = (Long) curval - 1*2;
				return (T)curval_v;
			}
			else if ((curval instanceof Integer)) {
				Integer curval_v = (Integer) curval - 1*2;
				return (T)curval_v;
			}
			else
				return curval;
		}

		private T add2Delta(T curval){
			if (curval instanceof Double) {
				Double curval_v = (Double) curval + EFCLONG_DOUBLE*2;
				return (T)curval_v;
			} else if (curval instanceof Float) {
				Float curval_v = (Float) curval + EFCLONG_FLOAT*2;
				return (T)curval_v;
			} else if ((curval instanceof Long)) {
				Long curval_v = (Long) curval + 1*2;
				return (T)curval_v;
			}
			else if ((curval instanceof Integer)) {
				Integer curval_v = (Integer) curval + 1*2;
				return (T)curval_v;
			}
			else
				return curval;
			}
		

		@SuppressWarnings("unchecked")
		public void AddRanges(Collection<RuleEntry<T>> ruleranges) {
			for (RuleEntry<T> entry : ruleranges) {
				addEntrytoSplitPoint(entry);
			}

			/* make all splitted ranges */
			Iterator<T> iter = splitpointset.iterator();
			T lb = null, rb = null;
			if (splitpointset.size() < 1) {
				throw new RuntimeException("splitpointset<2");
			}
			lb = iter.next();
			rb = iter.next();
			splitrangelist.add(new SplitRange<T>(lb, rb));
			int splitrangeidx = splitrangelist.size() - 1;
			splitrange_id_Map.put(Range.closed((lb), (rb)), splitrangeidx);

			while (iter.hasNext()) {
				lb = addDelta(rb);
				rb = minusDelta(iter.next());
				splitrangelist.add(new SplitRange<T>(lb, rb));
				splitrangeidx = splitrangelist.size() - 1;
				splitrange_id_Map.put(Range.closed((lb), (rb)), splitrangeidx);
			}

			/* filling up the rangeMap splitrangelist each entry */
			for (RuleEntry<T> range : ruleranges) {

				int lb_idx = -1;
				int rb_idx = -1;

				ComparablePair<Integer, Integer> splitidxrange = cptSplitrangeIdxRange(range);
				lb_idx = splitidxrange.getFirst();
				rb_idx = splitidxrange.getSecond();
				for (int i = lb_idx; i <= rb_idx ; i++) {
					if (splitrangelist.get(i) != null)
						splitrangelist.get(i).addRange(range);
				}
			}

		}

		private ComparablePair<Integer, Integer> cptSplitrangeIdxRange(RuleEntry<T> entry){
			int leftidx=-1,rightidx=-1;
			if (entry.hasLeftBound()) {
				T curval = entry.getLeftBound();
				BoundType bt = entry.getLeftBoundType();
//				if (bt == (BoundType.OPEN)) 
				{
					curval = add2Delta(curval);
					leftidx = splitrange_id_Map.get((T) curval);
//					if (curval instanceof Double) {
//						Double curval_v = (Double) curval + EFCLONG_DOUBLE*2;
//						leftidx = splitrange_id_Map.get((T) curval_v);
//					} else if (curval instanceof Float) {
//						Float curval_v = (Float) curval + EFCLONG_FLOAT*2;
//						leftidx = splitrange_id_Map.get((T) curval_v);
//					} else if ((curval instanceof Long)) {
//						Long curval_v = (Long) curval + 1*2;
//						leftidx = splitrange_id_Map.get((T) curval_v);
//					}
				}
			}
			if (entry.hasRightBound()) {
				T curval = entry.getRightBound();
				BoundType bt = entry.getRightBoundType();
//				if (bt == (BoundType.OPEN)) 
				curval = minus2Delta(curval);
				rightidx = splitrange_id_Map.get((T) curval);
//				{
//					if (curval instanceof Double) {
//						Double curval_v = (Double) curval - EFCLONG_DOUBLE*2;
//						rightidx = splitrange_id_Map.get((T) curval_v);
//					} else if (curval instanceof Float) {
//						Float curval_v = (Float) curval - EFCLONG_FLOAT*2;
//						rightidx = splitrange_id_Map.get((T) curval_v);
//					} else if ((curval instanceof Long)) {
//						Long curval_v = (Long) curval - 1*2;
//						rightidx = splitrange_id_Map.get((T) curval_v);
//					}
//				}
//				else {// closed
//					rightidx = splitrange_id_Map.get((T) curval);
//				}
			}
			
			return new ComparablePair<Integer, Integer>(leftidx, rightidx);
		}
		
		public Collection<BigInteger> getSatisifiedRules(T val) {
			Integer idx = splitrange_id_Map.get(val);
			if (idx != null)
				return splitrangelist.get(idx).refranges;
			else
				return new HashSet<BigInteger>();
		}

	}

	@SuppressWarnings({ "rawtypes", "unchecked", "unused" })
	/* key: tag index, value: all rule's related ruleentry on it */
	private static <T extends Comparable<T>> HashMultimap<Integer, RuleEntry> getTagRuleMap(
			Rule[] rules) {
		HashMultimap<Integer, RuleEntry> tagRuleMap = HashMultimap
				.<Integer, RuleEntry> create();

		for (int i = 0; i < rules.length; i++) {
			BigInteger rid = rules[i].getRuleID();
			RuleEntry[] entries = rules[i].getEntries();
			for (int j = 0; j < entries.length; j++) {
				entries[j].setRuleID(rid);
//				System.out.println(entries[j].getRange().toString());
				tagRuleMap.put(entries[j].getTagId(), entries[j]);
			}
		}
		return tagRuleMap;
	}

	// List<Rule> rules;
	@SuppressWarnings("rawtypes")
	public HashMap<Integer, TagNode> tagrules = new HashMap<Integer, TagNode>();

	public RangeRuleTreeNode(Rule[] rules) {
		// this.rules = Lists.newArrayList(rules);
		buildNode(rules);
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private void buildNode(Rule[] rules) {
		HashMultimap<Integer, RuleEntry> tagrulesmap = getTagRuleMap(rules);
		Map<Integer, Collection<RuleEntry>> realmap = tagrulesmap.asMap();
		for (Entry<Integer, Collection<RuleEntry>> entry : realmap.entrySet()) {
			int tagid = entry.getKey();
			//System.out.println("tagid:"+tagid);
			Collection<RuleEntry> ruleentries = entry.getValue();
			for (RuleEntry e : ruleentries) {
				//System.out.println(e.getRange().toString());
			}
			tagrules.put(tagid, new TagNode(tagid, ruleentries));
		}
	}
	
	public RangeRuleTreeNode(Collection<Rule> rules)
	{
		Rule[] rulearray = new Rule[rules.size()];
		rulearray = rules.toArray(rulearray);
		buildNode(rulearray);
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public Collection<BigInteger> executeAllRules(Comparable[] fact) {
		Set<BigInteger> result = null;

		for (int i = 0; i < fact.length; i++) {
			TagNode node = tagrules.get(i);

			if (node != null) {
				 Collection<BigInteger> coll4facti = node.getSatisifiedRules(fact[i]);
				if (result == null ){
					result = new TreeSet<BigInteger>();
					result.addAll(coll4facti);
				}
				else{
					result.retainAll(coll4facti);
				}
			}
		}
		return result;
	}

	public boolean addIncrementalRule(Rule rule) throws CPSException {
		throw new CPSException(ErrorCode.NO_IMPLEMENTATION_CODE,
				"do not support delete rule");
	}

	@Override
	public void deleteRuleByName(String ruleName) throws CPSException {
		throw new CPSException(ErrorCode.NO_IMPLEMENTATION_CODE,
				"do not support delete rule");
	}

	@Override
	public Collection<BigInteger> executeAllRules(ParsedDataMessage message) throws CPSException {
		return null;
	}

}
